Skip to content

S01-18 JavaSE-多线程基础

[TOC]

线程相关概念

程序(program)

  • 为完成特定任务,用某种语言编写的一组指令集合(即代码)

进程

  1. 运行中的程序(如 QQ、迅雷)
  2. 操作系统为进程分配独立内存空间
  3. 动态过程:有产生、存在、消亡的生命周期

线程

  1. 进程的实体,由进程创建
  2. 一个进程可包含多个线程(如 QQ 同时打开多个聊天窗口)
  3. 线程共享进程的内存空间

其他核心概念

概念说明
单线程同一时刻只允许执行一个线程
多线程同一时刻可执行多个线程
并发单核 CPU 中,多个任务交替执行(貌似同时)
并行多核 CPU 中,多个任务同时执行

线程基本使用

创建线程的两种方式

  1. 继承 Thread 类,重写 run 方法
  2. 实现 Runnable 接口,重写 run 方法

案例1:继承 Thread 类

java
package com.hspedu.threaduse;

/**
 * 演示继承 Thread 类创建线程
 * @author 韩顺平
 * @version 1.0
 */
public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        // 创建线程对象
        Cat cat = new Cat();
        cat.start(); // 启动线程(底层调用 start0() 方法)

        // 主线程继续执行
        System.out.println("主线程继续执行" + Thread.currentThread().getName());
        for (int i = 0; i < 60; i++) {
            System.out.println("主线程 i=" + i);
            Thread.sleep(1000);
        }
    }
}

class Cat extends Thread {
    int times = 0;

    @Override
    public void run() { // 线程执行的业务逻辑
        while (true) {
            System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000); // 休眠 1 秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (times == 80) {
                break; // 执行 80 次后退出
            }
        }
    }
}

案例2:实现 Runnable 接口

java
package com.hspedu.threaduse;

/**
 * 演示实现 Runnable 接口创建线程
 * @author 韩顺平
 * @version 1.0
 */
public class Thread02 {
    public static void main(String[] args) {
        Dog dog = new Dog();
        // 创建 Thread 对象,传入 Runnable 实现类
        Thread thread = new Thread(dog);
        thread.start();

        // 模拟 Thread 底层代理模式
        Tiger tiger = new Tiger();
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
    }
}

/**
 * 线程代理类(模拟 Thread 底层实现)
 */
class ThreadProxy implements Runnable {
    private Runnable target = null;

    public ThreadProxy(Runnable target) {
        this.target = target;
    }

    @Override
    public void run() {
        if (target != null) {
            target.run(); // 动态绑定
        }
    }

    public void start() {
        start0(); // 模拟 start0() 本地方法
    }

    private void start0() {
        run();
    }
}

class Animal {}

class Tiger extends Animal implements Runnable {
    @Override
    public void run() {
        System.out.println("老虎嗷嗷叫....");
    }
}

class Dog implements Runnable {
    int count = 0;

    @Override
    public void run() {
        while (true) {
            System.out.println("小狗汪汪叫..hi" + (++count) + " 线程名=" + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}

案例3:多线程并发执行

java
package com.hspedu.threaduse;

/**
 * 两个线程并发执行
 * @author 韩顺平
 * @version 1.0
 */
public class Thread03 {
    public static void main(String[] args) {
        // 线程1:输出 hello,world 10 次
        Thread thread1 = new Thread(new T1());
        // 线程2:输出 hi 5 次
        Thread thread2 = new Thread(new T2());
        thread1.start();
        thread2.start();
    }
}

class T1 implements Runnable {
    int count = 0;

    @Override
    public void run() {
        while (true) {
            System.out.println("hello,world " + (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}

class T2 implements Runnable {
    int count = 0;

    @Override
    public void run() {
        while (true) {
            System.out.println("hi " + (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 5) {
                break;
            }
        }
    }
}

继承 Thread vs 实现 Runnable

对比项继承 Thread实现 Runnable
继承限制单继承,灵活性低无继承限制,更灵活
资源共享需静态变量实现共享天然支持多线程共享资源
推荐度较低较高(推荐使用)

线程终止

核心思想:通过标记变量控制 run 方法退出

java
package com.hspedu.exit_;

/**
 * 线程终止示例
 * @author 韩顺平
 * @version 1.0
 */
public class ThreadExit_ {
    public static void main(String[] args) throws InterruptedException {
        AThread aThread = new AThread();
        new Thread(aThread).start();

        // 主线程执行 3 秒后终止子线程
        for (int i = 0; i < 30; i++) {
            System.out.println("main 线程运行中 " + i);
            Thread.sleep(100);
            if (i == 29) {
                aThread.setLoop(false); // 通知子线程退出
            }
        }
    }
}

class AThread implements Runnable {
    private boolean loop = true; // 标记变量

    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(50); // 休眠 50 毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("AThread 运行中....");
        }
    }

    // 提供 set 方法修改标记
    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

线程常用方法

常用方法第一组

方法功能
setName(String name)设置线程名称
getName()获取线程名称
start()启动线程(底层调用 start0()
run()线程执行的业务逻辑
setPriority(int newPriority)设置线程优先级(1-10)
getPriority()获取线程优先级
sleep(long millis)让当前线程休眠指定毫秒数
interrupt()中断线程(常用于唤醒休眠线程)

常用方法第二组

方法功能
yield()线程礼让(让出 CPU,不一定成功)
join()线程插队(插队线程执行完毕后,原线程才继续)

案例:join 方法使用

java
package com.hspedu.method;

/**
 * 线程插队示例
 * @author 韩顺平
 * @version 1.0
 */
public class ThreadMethodExercise {
    public static void main(String[] args) throws InterruptedException {
        Thread t3 = new Thread(new T3()); // 子线程

        for (int i = 1; i <= 10; i++) {
            System.out.println("hi " + i);
            if (i == 5) {
                t3.start(); // 启动子线程
                t3.join(); // 子线程插队,执行完毕后主线程继续
            }
            Thread.sleep(1000);
        }
    }
}

class T3 implements Runnable {
    private int count = 0;

    @Override
    public void run() {
        while (true) {
            System.out.println("hello " + (++count));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}

用户线程和守护线程

线程类型说明示例
用户线程(工作线程)任务执行完或通知后结束业务线程
守护线程为用户线程服务,所有用户线程结束后自动退出垃圾回收机制

守护线程示例

java
package com.hspedu.method;

/**
 * 守护线程示例
 * @author 韩顺平
 * @version 1.0
 */
public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread dt = new MyDaemonThread();
        dt.setDaemon(true); // 设置为守护线程
        dt.start();

        // 主线程执行 10 次后退出
        for (int i = 1; i <= 10; i++) {
            System.out.println("宝强辛苦工作....." + i);
            Thread.sleep(50);
        }
    }
}

class MyDaemonThread extends Thread {
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("马蓉和宋喆快乐聊天,哈哈哈~~~");
        }
    }
}

线程的生命周期

线程状态(Thread.State 枚举)

状态说明
NEW线程尚未启动
RUNNABLE线程正在 Java 虚拟机中执行
BLOCKED线程被阻塞等待监视器锁定
WAITING线程等待另一个线程执行特定动作
TIMED_WAITING线程等待指定时间后自动唤醒
TERMINATED线程已退出

查看线程状态示例

java
package com.hspedu.state_;

/**
 * 查看线程状态
 * @author 韩顺平
 * @version 1.0
 */
public class ThreadState_ {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        System.out.println(t.getName() + " 状态: " + t.getState()); // NEW

        t.start();
        while (Thread.State.TERMINATED != t.getState()) {
            System.out.println(t.getName() + " 状态: " + t.getState()); // RUNNABLE/TIMED_WAITING
            Thread.sleep(500);
        }
        System.out.println(t.getName() + " 状态: " + t.getState()); // TERMINATED
    }
}

class T extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("hi " + i);
            try {
                Thread.sleep(1000); // 进入 TIMED_WAITING 状态
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

线程的同步

问题背景:多线程共享资源冲突(如售票超卖)

Synchronized 同步机制

  1. 同步代码块
    java
    synchronized (对象) {
        // 需同步的代码(同一时刻只能有一个线程执行)
    }
  2. 同步方法
    java
    public synchronized void method() {
        // 需同步的代码
    }

互斥锁

  1. 每个对象对应一个互斥锁标记
  2. synchronized 修饰的代码块/方法需获取对象锁才能执行
  3. 静态同步方法的锁为 当前类.class
  4. 非静态同步方法的锁为 this

案例:同步解决售票问题

java
package com.hspedu.syn;

import java.util.Vector;

/**
 * 同步机制解决售票超卖问题
 * @author 韩顺平
 * @version 1.0
 */
public class SellTicket {
    public static void main(String[] args) {
        SellTicket03 sellTicket03 = new SellTicket03();
        // 三个窗口同时售票
        new Thread(sellTicket03, "窗口1").start();
        new Thread(sellTicket03, "窗口2").start();
        new Thread(sellTicket03, "窗口3").start();
    }
}

class SellTicket03 implements Runnable {
    private int ticketNum = 100; // 共享票数
    private boolean loop = true;
    Object object = new Object(); // 锁对象

    @Override
    public void run() {
        while (loop) {
            sell(); // 调用同步方法
        }
    }

    /**
     * 同步方法(锁为 this)
     */
    public synchronized void sell() {
        if (ticketNum <= 0) {
            System.out.println("售票结束...");
            loop = false;
            return;
        }

        // 模拟售票延迟
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("窗口" + Thread.currentThread().getName() + " 售出一张票" +
                " 剩余票数=" + (--ticketNum));
    }
}

线程的死锁

死锁条件:多个线程互相持有对方需要的锁,且不肯释放

死锁示例

java
package com.hspedu.syn;

/**
 * 模拟线程死锁
 * @author 韩顺平
 * @version 1.0
 */
public class DeadLock_ {
    public static void main(String[] args) {
        DeadLockDemo A = new DeadLockDemo(true);
        DeadLockDemo B = new DeadLockDemo(false);
        A.setName("A 线程");
        B.setName("B 线程");
        A.start();
        B.start();
    }
}

class DeadLockDemo extends Thread {
    static Object o1 = new Object(); // 共享锁对象1
    static Object o2 = new Object(); // 共享锁对象2
    boolean flag;

    public DeadLockDemo(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            // 线程A:先获取 o1,再尝试获取 o2
            synchronized (o1) {
                System.out.println(Thread.currentThread().getName() + " 进入1");
                synchronized (o2) {
                    System.out.println(Thread.currentThread().getName() + " 进入2");
                }
            }
        } else {
            // 线程B:先获取 o2,再尝试获取 o1
            synchronized (o2) {
                System.out.println(Thread.currentThread().getName() + " 进入3");
                synchronized (o1) {
                    System.out.println(Thread.currentThread().getName() + " 进入4");
                }
            }
        }
    }
}

释放锁的场景

会释放锁的操作

  1. 同步代码块/方法执行结束
  2. 同步代码块/方法中遇到 breakreturn
  3. 同步代码块/方法中抛出未处理的 ErrorException
  4. 同步代码块/方法中调用 wait() 方法

不会释放锁的操作

  1. 调用 Thread.sleep()Thread.yield() 方法
  2. 调用 suspend() 方法挂起线程(已过时)

本章作业

  1. Homework01.java(5min)
    • 启动两个线程:线程1随机打印100以内整数,线程2读取键盘输入"Q"命令终止线程1
  2. Homework02.java(5min)
    • 模拟银行取款:多个线程同时取款,每次取1000,余额不足时无法取款,避免超取(线程同步)